package org.cellocad.MIT.dnacompiler;


import org.cellocad.adaptors.ucfadaptor.UCFAdaptor;

import java.util.ArrayList;
import java.util.HashSet;

public class GeneticLocationWriter {


    public static void set_bp_range_for_parts(ArrayList<Part> parts, int start_bp, int end_bp) {
        for(Part p: parts) {
            p.set_start(start_bp);
            p.set_end(end_bp);
        }
    }

    public static void insertModulePartsIntoGeneticLocations(LogicCircuit lc, UCF ucf) {

        UCFAdaptor ucf_adaptor = new UCFAdaptor();

        if(!ucf_adaptor.getLocationName(ucf, "sensor").equals("")) {
            for (ArrayList<Part> sensor_module : lc.get_sensor_module_parts() )  {
                Integer start_bp = ucf_adaptor.getLocationStartBP(ucf, "sensor");
                Integer end_bp   = ucf_adaptor.getLocationStartBP(ucf, "sensor");
                GeneticLocationWriter.set_bp_range_for_parts(sensor_module, start_bp, end_bp);
            }
        }

        if(!ucf_adaptor.getLocationName(ucf, "circuit").equals("")) {
            for (ArrayList<Part> circuit_module : lc.get_circuit_module_parts()) {
                Integer start_bp = ucf_adaptor.getLocationStartBP(ucf, "circuit");
                Integer end_bp   = ucf_adaptor.getLocationStartBP(ucf, "circuit");
                GeneticLocationWriter.set_bp_range_for_parts(circuit_module, start_bp, end_bp);
            }
        }

        if(!ucf_adaptor.getLocationName(ucf, "output").equals("")) {
            for (ArrayList<Part> output_module : lc.get_output_module_parts()) {
                Integer start_bp = ucf_adaptor.getLocationStartBP(ucf, "output");
                Integer end_bp   = ucf_adaptor.getLocationStartBP(ucf, "output");
                GeneticLocationWriter.set_bp_range_for_parts(output_module, start_bp, end_bp);
            }
        }


        String sensor_location  = ucf_adaptor.getLocationName(ucf, "sensor");
        String circuit_location = ucf_adaptor.getLocationName(ucf, "circuit");
        String output_location  = ucf_adaptor.getLocationName(ucf, "output");

        if(!sensor_location.equals("")) {
            //if sensor module has its own unique location
            if (!sensor_location.equals(circuit_location) && !sensor_location.equals(output_location)) {
                writeModulesToLocation(
                        ucf,
                        "sensor",
                        lc.get_sensor_module_parts(),
                        lc.get_sensor_plasmid_parts() //the list of plasmids is generated by fn call
                );
            }
        }

        if(!circuit_location.equals("")) {
            //if circuit module has its own unique location
            if (!circuit_location.equals(sensor_location) && !circuit_location.equals(output_location)) {
                writeModulesToLocation(
                        ucf,
                        "circuit",
                        lc.get_circuit_module_parts(),
                        lc.get_circuit_plasmid_parts() //the list of plasmids is generated by fn call
                );
            }
        }

        if(!output_location.equals("")) {
            //if output module has its own unique location
            if (!output_location.equals(circuit_location) && !output_location.equals(sensor_location)) {
                writeModulesToLocation(
                        ucf,
                        "output",
                        lc.get_output_module_parts(),
                        lc.get_output_plasmid_parts() //the list of plasmids is generated by fn call
                );
            }
        }

        if(!sensor_location.equals("") && !sensor_location.equals("")) {
            //if sensor and circuit share a location
            if (sensor_location.equals(circuit_location) && !sensor_location.equals(output_location)) {
                writeModulesToLocation(
                        ucf,
                        "sensor",
                        lc.get_sensor_module_parts(),
                        "circuit",
                        lc.get_circuit_module_parts(),
                        lc.get_circuit_plasmid_parts() //the list of plasmids is generated by fn call
                );
            }
        }

        if(!sensor_location.equals("") && !output_location.equals("")) {
            //if sensor and output share a location
            if (sensor_location.equals(output_location) && !sensor_location.equals(circuit_location)) {
                writeModulesToLocation(
                        ucf,
                        "sensor",
                        lc.get_sensor_module_parts(),
                        "output",
                        lc.get_output_module_parts(),
                        lc.get_output_plasmid_parts() //the list of plasmids is generated by fn call
                );
            }
        }

        if(!circuit_location.equals("") && !output_location.equals("")) {
            //if circuit and output share a location
            if (circuit_location.equals(output_location) && !circuit_location.equals(sensor_location)) {
                writeModulesToLocation(
                        ucf,
                        "circuit",
                        lc.get_circuit_module_parts(),
                        "output",
                        lc.get_output_module_parts(),
                        lc.get_circuit_plasmid_parts() //the list of plasmids is generated by fn call
                );
            }
        }

        if(!sensor_location.equals("") && !circuit_location.equals("") && !output_location.equals("")) {
            //if sensor and circuit and output share a location


            if (circuit_location.equals(output_location) && circuit_location.equals(sensor_location)) {
                writeModulesToLocation(
                        ucf,
                        "sensor",
                        lc.get_sensor_module_parts(),
                        "circuit",
                        lc.get_circuit_module_parts(),
                        "output",
                        lc.get_output_module_parts(),
                        lc.get_circuit_plasmid_parts() //the list of plasmids is generated by fn call
                );
            }
        }

    }

    
    public static void writeModulesToLocation(
            UCF ucf,
            String module_name,
            ArrayList<ArrayList<Part>> module_parts,
            ArrayList<ArrayList<Part>> plasmid_list
    ) {


        UCFAdaptor ucf_adaptor = new UCFAdaptor();

        ArrayList<String> genbank_lines = ucf_adaptor.getLocationGenbankLines(ucf, module_name);
        Integer start_bp = ucf_adaptor.getLocationStartBP(ucf, module_name);
        Integer end_bp = ucf_adaptor.getLocationEndBP(ucf, module_name);

        String backbone_seq = PlasmidUtil.extractNucleotideSequenceFromGenbankLines(genbank_lines);

        //Step 1. Make a sorted list of unique cut sites
        HashSet<Integer> cut_sites_map = new HashSet<Integer>();
        cut_sites_map.add(1);
        cut_sites_map.add(start_bp);
        cut_sites_map.add(end_bp);
        cut_sites_map.add(backbone_seq.length());
        ArrayList<Integer> cut_sites = new ArrayList<Integer>(cut_sites_map);
        java.util.Collections.sort(cut_sites);

//        System.out.println(cut_sites.toString());

        //Step 2. Divide the backbone into segments based on the cuts.  Excise gaps when appropriate.
        ArrayList<Part> backbone_parts = new ArrayList<Part>();
        for(int i=0; i<cut_sites.size()-1; ++i) {
            if(cut_sites.get(i+1) > cut_sites.get(i)) {
                Part p = new Part("backbone", "backbone", backbone_seq.substring(cut_sites.get(i)-1, cut_sites.get(i+1)-1));
                p.set_start(cut_sites.get(i));
                p.set_end(cut_sites.get(i+1));

                //excision of gap sequence
                if(p.get_start() == start_bp && p.get_end() == end_bp) {
                    continue;
                }

                backbone_parts.add(p);
            }
        }

        for(ArrayList<Part> module: module_parts) {

            //Step 3. Assign the part start/end bp numbers according to the genetic location start/end from the UCF.
            //Note that all parts will share the same start/end.
            //Sorting parts based on start/end values will maintain the current order if start/end are identical.
            for (Part p : module) {
                p.set_start(start_bp);
                p.set_end(end_bp);
            }

            ArrayList<Part> plasmid = new ArrayList<Part>();
            plasmid.addAll(module);
            for(Part p: backbone_parts) {
                plasmid.add(new Part(p));
            }

            //Step 4. Sort parts based on start/end bp number, then renumber all parts in the plasmid.
            //Sorting MUST maintain the intended part order.
            PlasmidUtil.sortPartsByStartBP(plasmid);

            PlasmidUtil.renumberPlasmidBases(plasmid, 0);

            //Step 5. Append plasmid to list of plasmids.
            plasmid_list.add(plasmid);
        }
    }


    public static void writeModulesToLocation(
            UCF ucf,
            String module_name_1,
            ArrayList<ArrayList<Part>> module_parts_1,
            String module_name_2,
            ArrayList<ArrayList<Part>> module_parts_2,
            ArrayList<ArrayList<Part>> plasmid_list
    ) {

        UCFAdaptor ucf_adaptor = new UCFAdaptor();

        ArrayList<String> genbank_lines_1 = ucf_adaptor.getLocationGenbankLines(ucf, module_name_1);
        Integer start_bp_1 = ucf_adaptor.getLocationStartBP(ucf, module_name_1);
        Integer end_bp_1 = ucf_adaptor.getLocationEndBP(ucf, module_name_1);

        ArrayList<String> genbank_lines_2 = ucf_adaptor.getLocationGenbankLines(ucf, module_name_2);
        Integer start_bp_2 = ucf_adaptor.getLocationStartBP(ucf, module_name_2);
        Integer end_bp_2 = ucf_adaptor.getLocationEndBP(ucf, module_name_2);

        String backbone_seq = PlasmidUtil.extractNucleotideSequenceFromGenbankLines(genbank_lines_1);

        //Step 1. Make a sorted list of unique cut sites
        HashSet<Integer> cut_sites_map = new HashSet<Integer>();
        cut_sites_map.add(1);
        cut_sites_map.add(start_bp_1);
        cut_sites_map.add(end_bp_1);
        cut_sites_map.add(start_bp_2);
        cut_sites_map.add(end_bp_2);
        cut_sites_map.add(backbone_seq.length());
        ArrayList<Integer> cut_sites = new ArrayList<Integer>(cut_sites_map);
        java.util.Collections.sort(cut_sites);

//        System.out.println(cut_sites.toString());

        //Step 2. Divide the backbone into segments based on the cuts.  Excise gaps when appropriate.
        ArrayList<Part> backbone_parts = new ArrayList<Part>();
        for(int i=0; i<cut_sites.size()-1; ++i) {
            if(cut_sites.get(i+1) > cut_sites.get(i)) {
                Part p = new Part("backbone", "backbone", backbone_seq.substring(cut_sites.get(i)-1, cut_sites.get(i+1)-1));
                p.set_start(cut_sites.get(i));
                p.set_end(cut_sites.get(i+1));

                //excision of gap sequence
                if(p.get_start() == start_bp_1 && p.get_end() == end_bp_1) {
                    continue;
                }
                if(p.get_start() == start_bp_2 && p.get_end() == end_bp_2) {
                    continue;
                }

                backbone_parts.add(p);
            }
        }

        for(ArrayList<Part> module1: module_parts_1) {

            for(ArrayList<Part> module2: module_parts_2) { //nested loop will enumerate all combinations of module variants

                //Step 3. Assign the part start/end bp numbers according to the genetic location start/end from the UCF.
                //Note that all parts will share the same start/end.
                //Sorting parts based on start/end values will maintain the current order if start/end are identical.
                for (Part p : module1) {
                    p.set_start(start_bp_1);
                    p.set_end(end_bp_1);
                }
                for (Part p : module2) {
                    p.set_start(start_bp_2);
                    p.set_end(end_bp_2);
                }

                ArrayList<Part> plasmid = new ArrayList<Part>();
                plasmid.addAll(module1);
                plasmid.addAll(module2);
                for(Part p: backbone_parts) {
                    plasmid.add(new Part(p));
                }


                //Step 4. Sort parts based on start/end bp number, then renumber all parts in the plasmid.
                //Sorting MUST maintain the intended part order.
                PlasmidUtil.sortPartsByStartBP(plasmid);

                PlasmidUtil.renumberPlasmidBases(plasmid, 0);

                //Step 5. Append plasmid to list of plasmids.
                plasmid_list.add(plasmid);

            }
        }

    }


    public static void writeModulesToLocation(
            UCF ucf,
            String module_name_1,
            ArrayList<ArrayList<Part>> module_parts_1,
            String module_name_2,
            ArrayList<ArrayList<Part>> module_parts_2,
            String module_name_3,
            ArrayList<ArrayList<Part>> module_parts_3,
            ArrayList<ArrayList<Part>> plasmid_list
    ) {

        UCFAdaptor ucf_adaptor = new UCFAdaptor();

        ArrayList<String> genbank_lines_1 = ucf_adaptor.getLocationGenbankLines(ucf, module_name_1);
        Integer start_bp_1 = ucf_adaptor.getLocationStartBP(ucf, module_name_1);
        Integer end_bp_1 = ucf_adaptor.getLocationEndBP(ucf, module_name_1);

        ArrayList<String> genbank_lines_2 = ucf_adaptor.getLocationGenbankLines(ucf, module_name_2);
        Integer start_bp_2 = ucf_adaptor.getLocationStartBP(ucf, module_name_2);
        Integer end_bp_2 = ucf_adaptor.getLocationEndBP(ucf, module_name_2);

        ArrayList<String> genbank_lines_3 = ucf_adaptor.getLocationGenbankLines(ucf, module_name_3);
        Integer start_bp_3 = ucf_adaptor.getLocationStartBP(ucf, module_name_3);
        Integer end_bp_3 = ucf_adaptor.getLocationEndBP(ucf, module_name_3);
        
        String backbone_seq = PlasmidUtil.extractNucleotideSequenceFromGenbankLines(genbank_lines_1);

        //Step 1. Make a sorted list of unique cut sites
        HashSet<Integer> cut_sites_map = new HashSet<Integer>();
        cut_sites_map.add(1);
        cut_sites_map.add(start_bp_1);
        cut_sites_map.add(end_bp_1);
        cut_sites_map.add(start_bp_2);
        cut_sites_map.add(end_bp_2);
        cut_sites_map.add(start_bp_3);
        cut_sites_map.add(end_bp_3);
        cut_sites_map.add(backbone_seq.length());
        ArrayList<Integer> cut_sites = new ArrayList<Integer>(cut_sites_map);
        java.util.Collections.sort(cut_sites);

//        System.out.println(cut_sites.toString());

        //Step 2. Divide the backbone into segments based on the cuts.  Excise gaps when appropriate.
        ArrayList<Part> backbone_parts = new ArrayList<Part>();
        for(int i=0; i<cut_sites.size()-1; ++i) {
            if(cut_sites.get(i+1) > cut_sites.get(i)) {
                Part p = new Part("backbone", "backbone", backbone_seq.substring(cut_sites.get(i)-1, cut_sites.get(i+1)-1));
                p.set_start(cut_sites.get(i));
                p.set_end(cut_sites.get(i+1));

                //excision of gap sequence
                if(p.get_start() == start_bp_1 && p.get_end() == end_bp_1) {
                    continue;
                }
                if(p.get_start() == start_bp_2 && p.get_end() == end_bp_2) {
                    continue;
                }
                if(p.get_start() == start_bp_3 && p.get_end() == end_bp_3) {
                    continue;
                }

                backbone_parts.add(p);
            }
        }


        for(ArrayList<Part> module1: module_parts_1) {

            for(ArrayList<Part> module2: module_parts_2) {

                for(ArrayList<Part> module3: module_parts_3) { //nested loop will enumerate all combinations of module variants

                    //Step 3. Assign the part start/end bp numbers according to the genetic location start/end from the UCF.
                    //Note that all parts will share the same start/end.
                    //Sorting parts based on start/end values will maintain the current order if start/end are identical.
                    for (Part p : module1) {
                        p.set_start(start_bp_1);
                        p.set_end(end_bp_1);
                    }
                    for (Part p : module2) {
                        p.set_start(start_bp_2);
                        p.set_end(end_bp_2);
                    }
                    for (Part p : module3) {
                        p.set_start(start_bp_3);
                        p.set_end(end_bp_3);
                    }
                    ArrayList<Part> plasmid = new ArrayList<Part>();
                    plasmid.addAll(module1);
                    plasmid.addAll(module2);
                    plasmid.addAll(module3);
                    for(Part p: backbone_parts) {
                        plasmid.add(new Part(p));
                    }


                    //Step 4. Sort parts based on start/end bp number, then renumber all parts in the plasmid.
                    //Sorting MUST maintain the intended part order.
                    PlasmidUtil.sortPartsByStartBP(plasmid);

                    PlasmidUtil.renumberPlasmidBases(plasmid, 0);


                    //Step 5. Append plasmid to list of plasmids.
                    plasmid_list.add(plasmid);
                }

            }
        }


    }


}
